import os
import thirdparty.ply as ply
import compilerdefinitions
import compilererrors
import cpp


class __CompilerSettings:
    def __init__(self):
        self.linesizes = []
    
    # tokenizer
    reserved = {
       "const": "CONST",
       "enum": "ENUM",
       "struct": "STRUCT",
       "int_8": "INT_8",
       "uint_8": "UINT_8",
       "int_16": "INT_16",
       "uint_16": "UINT_16",
       "int_32": "INT_32",
       "uint_32": "UINT_32",
       "int_64": "INT_64",
       "uint_64": "UINT_64",
       "float_32": "FLOAT_32",
       "float_64": "FLOAT_64",
       "string": "STRING",
       "array": "ARRAY"
    }
    
    tokens = [
        "COMMENT",
        "MULTI_LINE_COMMENT",
        "NAME",
        "INT_CONST",
        "FLOAT_CONST",
        "STRING_CONST",
        "LCB",
        "RCB",
        "LAB",
        "RAB",
        "COMMA",
        "EQ",
        "TERMINATOR"
        ] + list(reserved.values())
    
    t_ignore_COMMENT = r"//.*"
    
    t_INT_CONST = r"[-]{0,1}[0-9]+"
    t_FLOAT_CONST = r"[-]{0,1}[0-9]+\.[0-9]+"
    t_STRING_CONST = r"\".*\"" 
    t_LCB = r"\{"
    t_RCB = r"\}"
    t_LAB = r"\<"
    t_RAB = r"\>"
    t_COMMA = r"\,"
    t_EQ = r"\="
    t_TERMINATOR = r"\;"
    
    t_ignore = " \t\r"
    
    def t_newline(self, t):
        r'\n'
        self.linesizes.append(t.lexer.lexpos)
        t.lexer.lineno += 1
    
    def t_ignore_MULTI_LINE_COMMENT(self, t):
        r"/\*[\w\W]*?\*/"
        for c in t.value:
            if c == '\n':
                self.linesizes.append(t.lexer.lexpos)
                t.lexer.lineno += 1
    
    def t_NAME(self, t):
        r"[a-zA-Z_]\w*"
        t.type = self.reserved.get(t.value, 'NAME')
        return t
    
    def t_error(self, t):
        l = 0
        if t.lexer.lineno > 1:
            l = self.linesizes[t.lexer.lineno - 2]
        raise compilererrors.LexerError(t.value[0], str(t.lexer.lineno), str(t.lexer.lexpos - l + 1))
    
    # parser
    def p_expressions(self, p):
        "expressions : expression expressions"
        p[0] = [p[1]] + p[2]
    
    def p_expressions_empty(self, p):
        "expressions : empty"
        p[0] = []
    
    def p_empty(self, p):
        "empty : "
        pass
    
    def p_empty_comment(self, p):
        "empty : COMMENT"
        pass
    
    def p_empty_multi_line_comment(self, p):
        "empty : MULTI_LINE_COMMENT"
        pass
    
    def p_number(self, p):
        """number   : INT_CONST
                    | FLOAT_CONST"""
        p[0] = p[1]
    
    def p_base_type(self, p):
        """base_type    : INT_8
                        | UINT_8
                        | INT_16
                        | UINT_16
                        | INT_32
                        | UINT_32
                        | INT_64
                        | UINT_64
                        | FLOAT_32
                        | FLOAT_64"""
        p[0] = compilerdefinitions.TypeDefinition(
            lineNumber = p.lineno(1),
            name = p[1],
            isUserDefined = False,
            argList = [])
    
    def p_string_type(self, p):
        "string_type : STRING LAB RAB"
        p[0] = compilerdefinitions.TypeDefinition(
            lineNumber = p.lineno(1),
            name = "string",
            isUserDefined = False,
            argList = [])
    
    def p_string_with_length_type(self, p):
        "string_type : STRING LAB number RAB"
        p[0] = compilerdefinitions.TypeDefinition(
            lineNumber = p.lineno(1),
            name = "string",
            isUserDefined = False,
            argList = [int(p[3])])
    
    def p_array_type(self, p):
        "array_type : ARRAY LAB type RAB"
        p[0] = compilerdefinitions.TypeDefinition(
            lineNumber = p.lineno(1),
            name = "array",
            isUserDefined = False,
            argList = [p[3]])
    
    def p_array_with_length_type(self, p):
        "array_type : ARRAY LAB type COMMA number RAB"
        p[0] = compilerdefinitions.TypeDefinition(
            lineNumber = p.lineno(1),
            name = "array",
            isUserDefined = False,
            argList = [p[3], int(p[5])])
    
    def p_user_defined_type(self, p):
        "user_defined_type : NAME"
        p[0] = compilerdefinitions.TypeDefinition(
            lineNumber = p.lineno(1),
            name = p[1],
            isUserDefined = True,
            argList = [])
    
    def p_type(self, p):
        """type     : base_type
                    | string_type
                    | array_type
                    | user_defined_type"""
        p[0] = p[1]
    
    def p_expression_const_base_type(self, p):
        "expression : CONST base_type NAME EQ number TERMINATOR"
        p[0] = compilerdefinitions.ConstDefinition(
            lineNumber = p.lineno(1),
            typeDefinition = p[2],
            name = p[3],
            valueString = p[5])
    
    def p_expression_const_string_type(self, p):
        "expression : CONST string_type NAME EQ STRING_CONST TERMINATOR"
        p[0] = compilerdefinitions.ConstDefinition(
            lineNumber = p.lineno(1),
            typeDefinition = p[2],
            name = p[3],
            valueString = p[5])
    
    def p_expression_enum(self, p):
        "expression : ENUM NAME LCB enum_name_definitions RCB TERMINATOR"
        p[0] = compilerdefinitions.EnumDefinition(
            lineNumber = p.lineno(1),
            name = p[2],
            nameDefinitions = p[4])
    
    def p_enum_name_definitions(self, p):
        "enum_name_definitions : enum_name_definition enum_name_definitions"
        p[0] = [p[1]] + p[2]
    
    def p_enum_name_definitions_empty(self, p):
        "enum_name_definitions : empty"
        p[0] = []
    
    def p_enum_name_definition(self, p):
        "enum_name_definition : NAME EQ INT_CONST TERMINATOR"
        p[0] = compilerdefinitions.EnumNameDefinition(
            lineNumber = p.lineno(1),
            name = p[1],
            valueString = p[3])
    
    def p_enum_name_definition_empty(self, p):
        "enum_name_definition : NAME TERMINATOR"
        p[0] = compilerdefinitions.EnumNameDefinition(
            lineNumber = p.lineno(1),
            name = p[1],
            valueString = None)
    
    def p_expression_struct(self, p):
        "expression : STRUCT NAME LCB struct_fields RCB TERMINATOR"
        p[0] = compilerdefinitions.StructDefinition(
            lineNumber = p.lineno(1),
            name = p[2],
            filedDefinitions = p[4])
    
    def p_struct_fields(self, p):
        "struct_fields : struct_field struct_fields"
        p[0] = [p[1]] + p[2]
    
    def p_struct_fields_empty(self, p):
        "struct_fields : empty"
        p[0] = []
    
    def p_struct_field_default_value_const(self, p):
        """field_default_value  : number
                                | STRING_CONST"""
        p[0] = compilerdefinitions.DefaultValue(
            lineNumber = p.lineno(1),
            valueString = p[1],
            name = None)
    
    def p_struct_field_default_value_name(self, p):
        "field_default_value : NAME"
        p[0] = compilerdefinitions.DefaultValue(
            lineNumber = p.lineno(1),
            valueString = None,
            name = p[1])
    
    def p_struct_field_value(self, p):
        "struct_field : type NAME EQ field_default_value TERMINATOR"
        p[0] = compilerdefinitions.StructFieldDefinition(
            lineNumber = p.lineno(1),
            typeDefinition = p[1],
            name = p[2],
            defaultValue = p[4])
    
    def p_struct_field(self, p):
        "struct_field : type NAME TERMINATOR"
        p[0] = compilerdefinitions.StructFieldDefinition(
            lineNumber = p.lineno(1),
            typeDefinition = p[1],
            name = p[2],
            defaultValue = None)
    
    def p_error(self, p):
        l = 0
        if p.lineno > 1:
            l = self.linesizes[p.lineno - 2]
        raise compilererrors.ParserError(p.value, p.lineno, p.lexpos - l + 1)


def __createFileWithDirectories(filePath):
    filePathNormalized = filePath.replace("\\", "/")
    (folder, _, _) = filePathNormalized.rpartition("/")
    
    if folder != "":
        if not os.path.exists(folder):
            os.makedirs(folder)
    
    return open(filePathNormalized, "w+t")


def Parse(fileName):
    try:
        f = open(fileName, "rt")
    except:
        raise compilererrors.CompilerError("Now such file: %s" % (fileName))
    protoText = f.read()
    
    compilerSettings = __CompilerSettings()
    
    lexer = ply.lex.lex(module = compilerSettings)
    lexer.prevlexpos = 0
    
    parser = ply.yacc.yacc(
        write_tables=0,
        debug=None,
        debuglog=None,
        errorlog=None,
        module=compilerSettings
        )
    p = parser.parse(protoText, lexer)
    
    return p


def RunCompiler(fileName, language, nameType, outFileName):
    p = Parse(fileName)
    
    outputText = ""
    if language == "cpp":
        outputText = cpp.OutputAsCpp(fileName, p, nameType)
    
    fileToWrtie = __createFileWithDirectories(outFileName)
    fileToWrtie.write(outputText)
    fileToWrtie.close()


def Test():
    compiled = Parse("test.protobuffik")
    print(cpp.OutputAsCpp("test.protobuffik", compiled, "int"))


def __main__():
    Test()


if __name__ == "__main__":
    __main__()